﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components.Surface;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Types;
using RayDen.SmallRT.Components;

namespace RayDen.SmallRT.Integrators
{
    internal class PathTracerDL : BaseIntegrator
    {
        public const int MaxDepth = 10;
        protected string stats;
        protected long totalSamples = 0, pass = 0;
        public override void InitPass()
        {

        }

        public override void RenderPass()
        {
            var sb = film.GetFreeSampleBuffer();
            var sw = Stopwatch.StartNew();
            var samplesPerPass = Scene.w*Scene.h;
            for (int y = 0; y < Scene.h; ++y)
            {
                for (int x = 0, pj = 0; x < Scene.w; ++x, ++pj)
                {
                    RayData cameraRay;
                    Scene.GenerateCameraRay(x, y, out cameraRay);
                    var pix = EvalRadiance(ref cameraRay, 0);
                    totalSamples++;
                    sb.SplatSample(x, (Scene.h-y-1), ref pix);
                    if (sb.IsFull())
                    {
                        film.SplatSampleBuffer(true,sb);
                        sb = film.GetFreeSampleBuffer();
                    }
                }
            }
            pass++;
            sw.Stop();
            stats = string.Format("MSamples per sec {1:F5}; Total Samples {0}; Samples per pixel {2}; {3}", totalSamples, 0.000001 * (samplesPerPass / sw.Elapsed.TotalSeconds), pass, film.GetStats());
        }

        public override string GetStats()
        {
            return stats;
        }

        private RgbSpectrum EvalRadiance(ref RayData r, int depth)
        {
            double t = 0; // distance to intersection 
            int id = 0; // id of intersected object 
            if (!Scene.Intersect(ref r, ref t, ref id) || depth > MaxDepth)
            {
                return RgbSpectrum.Black;
            }
            var obj = SceneManager.Sph[id];
            var x = r.Point((float)t);
            Normal n = (Normal) (x - obj.p).Normalize();
            var nl = Vector.Dot(ref n, ref r.Dir) < 0 ? n : n * -1;
            var f = obj.c;
            var e = RgbSpectrum.Black;
            var p = f.c1 > f.c2 && f.c1 > f.c3 ? f.c1 : f.c2 > f.c3 ? f.c2 : f.c3; // max refl 
            if (obj.refl == 3)
            {
                e += f;
            }
            if (++depth > MaxDepth)
            {
                if (rnd.NextFloat() < p)
                {
                    f = f*(1f/p);
                }
                else
                {
                    return e;
                }
            }
            var brdf = Brdfs[obj.refl];
            Vector dir;
            float fb;
            RgbSpectrum rad = new RgbSpectrum();

            brdf.Sample(ref r.Dir, ref n, rnd.NextFloat(), rnd.NextFloat(), 1f, out dir, out fb);

            if (id == 8)
            {
                return f;
            }
            if (brdf.BsdfType.Has(BsdfEvent.Diffuse))
            {
                int shadowRays = 4;
                for (var i = 0; i < shadowRays; i++)
                {
                    RayData shr = new RayData();
                    Scene.GenerateShadowRay(rnd.NextFloat(), rnd.NextFloat(), ref x, ref shr);
                    var l = x - shr.Org;
                    shr.maxT = l.Length;
                    double shd = 1e5f;
                    int tid = 0;

                    if (Scene.Intersect(ref shr, ref shd, ref tid))
                    {
                        if (tid == 8)
                        {
                            var w = -shr.Dir;
                            rad += ((SceneManager.Sph[8].c*Vector.AbsDot(ref nl, ref w)*f*fb)/l.Length)*(1.0f / shadowRays);
                        }
                    }
                    else
                    {
                        rad += SceneManager.Sph[8].c * Vector.AbsDot(ref nl, ref shr.Dir) * f / l.Length;
                    }
                }
            }

            RayData newRay = new RayData(ref x, ref dir);
            return rad+(e + f * fb * EvalRadiance(ref newRay, depth));
        }
    }
}
